Skip to content

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Nov 5, 2025

Add dynamic authentication guide for JWT signing in SDKs (restructured by language)

Summary

Adds comprehensive documentation for implementing dynamic authentication patterns (like short-lived JWT signing) in Fern-generated SDKs. The documentation is now organized by language per Swimburger's feedback:

Structure:

  • Overview hub (/sdks/deep-dives/dynamic-authentication) - Lightweight landing page with cards linking to language-specific guides
  • TypeScript guide (/sdks/generators/typescript/dynamic-authentication) - Custom fetcher middleware approach with JWT signing, token memoization, and comprehensive best practices
  • Python guide (/sdks/generators/python/dynamic-authentication) - Method override approach with JWT signing example

Key changes:

  • Reorganized all content by language (TypeScript under TypeScript docs, Python under Python docs)
  • Removed "Alternative: Method overrides" section from TypeScript docs per explicit feedback
  • TypeScript: Features custom fetcher middleware as the sole approach with allowCustomFetcher configuration
  • Python: Uses method override pattern (no custom fetcher support yet)
  • Added navigation entries for all three pages in sdks.yml
  • Uses plant-themed examples (PlantStoreClient, monstera-123, Fiddle Leaf Fig) throughout
  • All code uses ConstructorParameters pattern for type safety

Review & Testing Checklist for Human

⚠️ CRITICAL: The code examples were written based on codebase research but were NOT tested end-to-end in actual SDK projects. Please test thoroughly before merging.

  • Test TypeScript custom fetcher example - Generate a TypeScript SDK with allowCustomFetcher: true, implement the example code, and verify it works correctly with actual API calls
  • Test Python method override example - Generate a Python SDK, implement the example code, and verify JWT signing works correctly
  • Verify import paths - Confirm that import { fetcher as defaultFetcher, type FetchFunction } from "../core/fetcher" matches the actual import path in generated TypeScript SDKs with allowCustomFetcher enabled
  • Test navigation and links - Verify all internal links work (overview cards → language pages, "See also" links, etc.) and navigation in sidebar is intuitive
  • Check generator version requirements - Verify which TypeScript generator version introduced allowCustomFetcher and whether documentation should mention version requirements
  • Verify restructure meets expectations - Confirm with Swimburger that the language-by-language organization matches the intended structure

Test Plan

  1. Generate a TypeScript SDK with allowCustomFetcher: true enabled
  2. Implement the custom fetcher example from the docs
  3. Generate a Python SDK and implement the method override example
  4. Make several API calls in both languages and verify JWT tokens are correctly signed and included in headers
  5. Navigate through the documentation and test all internal links

Notes

@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring


### Use the client

Your users can now use the client with automatic JWT signing:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [vale] reported by reviewdog 🐶
[FernStyles.Current] Avoid time-relative terms like 'now' that become outdated

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.

## Best practices

- **Cache when possible**: If your tokens are valid for longer periods, cache them to avoid regenerating on every request
- **Handle errors gracefully**: Implement retry logic for authentication failures
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'gracefully' if it's not important to the meaning of the statement.

- **Cache when possible**: If your tokens are valid for longer periods, cache them to avoid regenerating on every request
- **Handle errors gracefully**: Implement retry logic for authentication failures
- **Secure key storage**: Never hardcode private keys; use environment variables or secure key management systems
- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'thoroughly' if it's not important to the meaning of the statement.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

@Swimburger
Copy link
Member

For TypeScript, this is the best approach:

  • Extend the custom client
  • In the constructor of the custom client, pass in a custom fetcher to the base class
  • In the custom fetcher you can run any logic you want and it acts like middleware for any request. This is a good place to put any sort of token "signing" logic with memoization.

The fetcher function should wrap FetchFunction = fetcherImpl that is usually found in Fetcher.ts. Do so as dynamically as possible so that any params passed to your custom fetcher implementation is also passed to the standard fetcher implementation.

There's no need for Proxy's. It's not maintainable to customize every client class, which is why you need to override the single Fetcher function that is reused everywhere.

- Add custom fetcher middleware as recommended approach per Swimburger feedback
- Replace Proxy pattern with simpler method override alternative
- Add comprehensive gotchas and security considerations
- Update all examples to use plant-themed content (PlantStoreClient, plants.get, etc.)
- Fix Vale linting issues: remove time-relative 'now', define HMAC acronym
- Add allowCustomFetcher configuration example
- Include token memoization pattern with grace period
- Document import paths, header merging, and thread safety considerations

Co-Authored-By: Chris McDonnell <[email protected]>

## Alternative: Method overrides

If you cannot enable `allowCustomFetcher` or prefer a simpler approach, you can override individual methods:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [vale] reported by reviewdog 🐶
[Microsoft.Contractions] Use 'can't' instead of 'cannot'.

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.


- **Use custom fetcher for TypeScript**: The custom fetcher approach is the most maintainable solution for TypeScript SDKs when `allowCustomFetcher` is available
- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'gracefully' if it's not important to the meaning of the statement.

- **Use custom fetcher for TypeScript**: The custom fetcher approach is the most maintainable solution for TypeScript SDKs when `allowCustomFetcher` is available
- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests, token expiration, and network failures
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'thoroughly' if it's not important to the meaning of the statement.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

@devin-ai-integration
Copy link
Contributor Author

Thanks for the feedback, @Swimburger! I've updated the documentation to feature the custom fetcher approach as the recommended solution.

Changes made:

  • Added custom fetcher middleware as the primary approach with a complete example showing JWT signing and token memoization
  • Included allowCustomFetcher: true configuration example in generators.yml
  • Replaced the Proxy pattern with a simpler method override alternative (demoted to "Alternative" section)
  • Added comprehensive gotchas section covering security, performance, header merging, and thread safety
  • Updated all examples to use plant-themed content per internal guidelines
  • Fixed Vale linting issues

The custom fetcher example wraps fetcherImpl from core/fetcher and injects JWT authentication with proper memoization and a 2-second grace period before token expiration. This provides a single injection point for all requests without needing to override individual methods.

Note: The code examples were written based on research of the Fern generator codebase but haven't been tested end-to-end in an actual SDK project. Would appreciate if someone could verify the import paths and implementation details are correct before merging.

import { createJwtFetcher } from "./jwtFetcher";

export class PlantStoreClient extends FernClient {
constructor(options: { privateKey: string; environment: string }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure you accept the same options as FernClient's ctor does, minus the custom fetcher, and dynamically pass it along.

- Use ConstructorParameters to infer exact options type from generated client
- Accept all client options (headers, timeout, maxRetries, etc.) except fetcher
- Extract privateKey and pass remaining options to parent constructor
- Add explanatory note about ConstructorParameters pattern
- Update both custom fetcher and method override examples for consistency

Addresses Swimburger's feedback about dynamically passing all options.

Co-Authored-By: Chris McDonnell <[email protected]>
@devin-ai-integration
Copy link
Contributor Author

Good catch! I've updated the wrapper client constructor to accept all the same options as FernClient (minus fetcher) and dynamically pass them along.

Changes made:

  • Used ConstructorParameters<typeof FernClient>[0] to infer the exact options type from the generated client
  • Used Omit<FernClientOptions, "fetcher"> to accept all options except fetcher
  • Destructured privateKey and spread the remaining clientOptions to the parent constructor
  • Added an explanatory note about why we use ConstructorParameters (keeps the wrapper future-proof)
  • Updated both the custom fetcher example and the method override alternative for consistency

This ensures the wrapper accepts all client options (headers, timeoutInSeconds, maxRetries, logging, etc.) without hardcoding them, making it compatible with current and future generator versions.

```

<Note>
This pattern uses `ConstructorParameters` to infer the exact options type from the generated client, ensuring compatibility with all client options (headers, timeoutInSeconds, maxRetries, etc.) without hardcoding them. This keeps the wrapper future-proof as the generator adds new options.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [vale] reported by reviewdog 🐶
[FernStyles.Current] Avoid time-relative terms like 'future' that become outdated

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025


</Steps>

## Alternative: Method overrides
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this section

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the TypeScript guidance under the TypeScript documentation, and the Python under the Python documentation.

@devin-ai-integration
Copy link
Contributor Author

Thanks for the feedback! Before I make these structural changes, I want to confirm the desired organization:

Proposed restructure:

  1. Keep a lightweight overview page at /learn/sdks/deep-dives/dynamic-authentication that:

    • Briefly explains the use case (dynamic auth, short-lived tokens, etc.)
    • Links to language-specific implementation guides
    • Stays language-agnostic
  2. Create TypeScript-specific page (e.g., under /learn/sdks/generators/typescript/) with:

    • Custom fetcher middleware approach (the recommended pattern)
    • Complete example with allowCustomFetcher, ConstructorParameters, JWT memoization
    • TypeScript-specific gotchas (import paths, this before super, etc.)
    • Remove method override alternative entirely per your feedback
  3. Create Python-specific page (e.g., under /learn/sdks/generators/python/) with:

    • Method override approach (since Python doesn't have custom fetcher yet)
    • Python-specific notes (request_options, key storage, etc.)
  4. Update navigation in sdks.yml to link to the new language-specific pages

Alternative: If you prefer no cross-language overview hub, I can delete dynamic-authentication.mdx entirely and only have the language-specific pages.

Which approach would you prefer? And where exactly should I place the TypeScript and Python pages in the docs structure?

@Swimburger
Copy link
Member

Implement your proposed structure

- Remove 'Alternative: Method overrides' section from TypeScript docs
- Create TypeScript-specific page with custom fetcher approach only
- Create Python-specific page with method override approach
- Convert main guide to lightweight overview hub with language cards
- Add navigation entries for both language-specific pages
- Keep language-agnostic considerations in overview

Addresses Swimburger's feedback to organize content by language.

Co-Authored-By: Chris McDonnell <[email protected]>
This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.


- **Override all methods**: Ensure you override all API methods to maintain consistent authentication across your SDK
- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'gracefully' if it's not important to the meaning of the statement.

- **Override all methods**: Ensure you override all API methods to maintain consistent authentication across your SDK
- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests, token expiration, and network failures
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'thoroughly' if it's not important to the meaning of the statement.

```

<Note>
This pattern uses `ConstructorParameters` to infer the exact options type from the generated client, ensuring compatibility with all client options (headers, timeoutInSeconds, maxRetries, etc.) without hardcoding them. This keeps the wrapper future-proof as the generator adds new options.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [vale] reported by reviewdog 🐶
[FernStyles.Current] Avoid time-relative terms like 'future' that become outdated

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.

This same pattern works for other dynamic authentication scenarios:

- **OAuth token refresh**: Automatically refresh expired access tokens before each request
- **HMAC (Hash-based Message Authentication Code) signing**: Sign requests with HMAC signatures based on request content
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'HMAC' has no definition.

## Best practices

- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'gracefully' if it's not important to the meaning of the statement.


- **Cache tokens appropriately**: Balance between security (shorter token lifetime) and performance (less frequent regeneration)
- **Handle errors gracefully**: Implement retry logic for authentication failures and token refresh errors
- **Test thoroughly**: Ensure your wrapper handles all edge cases, including concurrent requests, token expiration, and network failures
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 [vale] reported by reviewdog 🐶
[FernStyles.Adverbs] Remove 'thoroughly' if it's not important to the meaning of the statement.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

@devin-ai-integration
Copy link
Contributor Author

Done! I've implemented the restructure as proposed:

Changes made:

  • ✅ Removed "Alternative: Method overrides" section from TypeScript docs
  • ✅ Created TypeScript-specific page at /sdks/generators/typescript/dynamic-authentication with custom fetcher approach only
  • ✅ Created Python-specific page at /sdks/generators/python/dynamic-authentication with method override approach
  • ✅ Converted main guide to lightweight overview hub with language cards
  • ✅ Added navigation entries for both language-specific pages in sdks.yml
  • ✅ Kept language-agnostic considerations (security, performance, time sync) in the overview

Preview URLs:

All CI checks passing ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants